package com.leizi.igym.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch.core.DeleteResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.UpdateResponse;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leizi.igym.BaseContext.UserThreadLocal;
import com.leizi.igym.constant.LostConstant;
import com.leizi.igym.dao.LostESDao;
import com.leizi.igym.domain.Lost;
import com.leizi.igym.domain.User;
import com.leizi.igym.dto.LostAddDTO;
import com.leizi.igym.dto.LostDTO;
import com.leizi.igym.dto.LostESDTO;
import com.leizi.igym.dto.LostPageQueryDTO;
import com.leizi.igym.enums.ReturnCodeEnum;
import com.leizi.igym.exception.BusinessException;
import com.leizi.igym.mapper.LostMapper;
import com.leizi.igym.pojo.ContextUser;
import com.leizi.igym.service.LostService;
import com.leizi.igym.service.UserService;
import com.leizi.igym.utils.ThrowUtils;
import com.leizi.igym.vo.LostVO;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author PC
 * @description 针对表【t_lost】的数据库操作Service实现
 * @createDate 2024-04-19 22:33:57
 */
@Service
public class LostServiceImpl extends ServiceImpl<LostMapper, Lost>
        implements LostService {

    @Resource
    private UserService userService;

    @Resource
    private LostESDao lostESDao;

    @Resource
    private ElasticsearchClient esClient;

    @Override
    public boolean saveInAdminAndUser(LostAddDTO dto) {
        if (Objects.isNull(dto)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }

        String descrip = dto.getDescrip();
        String picture = dto.getPicture();
        String title = dto.getTitle();

        if (StringUtils.isAnyBlank(descrip, picture, title)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR);
        }

        Lost lost = new Lost();
        BeanUtils.copyProperties(dto, lost);

        // 获取当前用户，为失物招领部分属性必要属性赋值
        ContextUser contextUser = UserThreadLocal.get();
        Long userId = contextUser.getId();

        User dbUser = userService.getById(userId);

        if (Objects.isNull(dbUser)) {
            throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "当前登录用户异常！");
        }

        String nickname = dbUser.getNickname();
        String avatar = dbUser.getAvatar();
        if (StringUtils.isAnyBlank(nickname, avatar)) {
            throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "当前登录用户异常！");
        }

        lost.setAvatar(avatar);
        lost.setNickname(nickname);
        lost.setUserId(userId);
        lost.setCreateTime(LocalDateTime.now());
        lost.setUpdateTime(LocalDateTime.now());
        lost.setStatus(0);
        lost.setDeleted(0);


        Integer result = this.getBaseMapper().insert(lost);
        if (null == result || result < 1) {
            return false;
        }
        LostESDTO lostESDTO = new LostESDTO();
        BeanUtils.copyProperties(lost, lostESDTO);
        lostESDao.save(lostESDTO);
        return true;
    }

    @Override
    @Transactional
    public boolean deleteLostInAdminAndUser(Long id) throws IOException {
        ThrowUtils.throwIf(Objects.isNull(id) || id <= 0, ReturnCodeEnum.PARAMS_ERROR);
        // 失物招领信息
        Integer result = this.getBaseMapper().deleteById(id);
        if (null == result || result < 1) {
            return false;
        }
        DeleteResponse response = esClient.delete(res -> res
                .refresh(Refresh.True)
                .index(LostConstant.LOST_ES_INDEX)
                .id(id + ""));
        return Result.Deleted.equals(response.result());
    }

    @Override
    @Transactional
    public boolean deleteLostBatchInAdmin(List<Long> ids) {
        ThrowUtils.throwIf(CollectionUtil.isEmpty(ids), ReturnCodeEnum.PARAMS_ERROR);
        Integer result = this.getBaseMapper().deleteBatchIds(ids);
        if (null == result || result < 1) {
            return false;
        }
        lostESDao.deleteAllById(ids, RefreshPolicy.IMMEDIATE);
        return true;
    }

    @Override
    @Transactional
    public boolean updateInAdminAndUser(LostDTO dto) throws IOException {
        ThrowUtils.throwIf(Objects.isNull(dto), ReturnCodeEnum.PARAMS_ERROR);
        Lost lost = new Lost();
        BeanUtils.copyProperties(dto, lost);
        // 更新事务招领信息
        Integer result = this.getBaseMapper().updateById(lost);
        if (null == result || result < 1) {
            return false;
        }
        Lost updated = this.getById(dto);
        LostESDTO lostESDTO = new LostESDTO();
        BeanUtils.copyProperties(updated, lostESDTO);
        lostESDTO.setTitle(dto.getDescrip());
        lostESDTO.setDescrip(dto.getTitle());
        lostESDTO.setPicture(dto.getPicture());
        lostESDTO.setStatus(dto.getStatus());
        lostESDTO.setCreateTime(LocalDateTime.now());
        lostESDao.save(lostESDTO, RefreshPolicy.IMMEDIATE);
        return true;
    }

    @Override
    public Page<LostESDTO> getVOPage(LostPageQueryDTO dto) throws IOException {
        ThrowUtils.throwIf(Objects.isNull(dto), ReturnCodeEnum.PARAMS_ERROR);

        Integer status = dto.getStatus();
        String searchText = dto.getSearchText();
        LocalDate beginTime = dto.getBeginTime();
        LocalDate endTime = dto.getEndTime();
        Integer current = dto.getCurrent();
        Integer pageSize = dto.getPageSize();

        // 先判断传来时间是否合法
        if (ObjectUtils.allNotNull(beginTime, endTime) && beginTime.isAfter(endTime)) {
            throw new BusinessException(ReturnCodeEnum.PARAMS_ERROR, "传入的时间参数有误！");
        }

        // 执行bool查询
        BoolQuery.Builder boolBuilder = new BoolQuery.Builder();
        Query.Builder queryBuilder = new Query.Builder();
        SearchRequest.Builder searchBuilder = new SearchRequest.Builder();

        // 对状态进行查询
        if (LostConstant.LOST_STATUS_LIST.contains(status)) {
            // queryBuilder.match(m -> m.field("status")
            //         .query(status));
            boolBuilder.must(s -> s.match(m -> m.field("status")
                    .query(status)));
        }

        // 对搜索文本进行查询
        if (StringUtils.isNoneBlank(searchText)) {
            boolBuilder.should(s -> s.match(m -> m.field("title")
                    .query(searchText)));
            boolBuilder.should(s -> s.match(m -> m.field("descrip")
                    .query(searchText)));
            boolBuilder.should(s -> s.match(m -> m.field("nickname")
                    .query(searchText)));
            boolBuilder.minimumShouldMatch("1");
        }
        boolBuilder.filter(f -> f
                .match(m -> m
                        .field("deleted")
                        .query(0)));


        // 创建范围查询器
        if (ObjectUtils.anyNotNull(beginTime, endTime)) {
            RangeQuery.Builder rangeBuilder = new RangeQuery.Builder();
            if (!Objects.isNull(beginTime)) {
                rangeBuilder.field("createTime").gte(JsonData.of(beginTime));
            }

            if (!Objects.isNull(endTime)) {
                rangeBuilder.field("createTime").lte(JsonData.of(endTime));
            }
            RangeQuery rangeQuery = rangeBuilder.build()._toQuery().range();
            boolBuilder.must(m -> m.range(rangeQuery));
            // searchBuilder.query(q -> q.range(rangeQuery));
        }
        BoolQuery boolQuery = boolBuilder.build();

        Highlight.Builder highlightBuilder = new Highlight.Builder();
        highlightBuilder.fields("title", hf -> hf
                        .preTags("<span style='color:red;font-weight: bold'>")
                        .postTags("</span>"))
                .fields("descrip", hf -> hf
                        .preTags("<span style='color:red;font-weight: bold'>")
                        .postTags("</span>"))
                .fields("nickname", hf -> hf
                        .preTags("<span style='color:red;font-weight: bold'>")
                        .postTags("</span>"));
        Highlight highlight = highlightBuilder.build();
        searchBuilder.index(LostConstant.LOST_ES_INDEX).query(q -> q
                        .bool(boolQuery))
                .highlight(highlight)
                .from((current - 1) * pageSize)
                .size(pageSize)
                .sort(s -> s
                        .field(f -> f.field("status")
                                .order(SortOrder.Asc))
                )
                .sort(s -> s.field(f -> f.field("createTime")
                        .order(SortOrder.Desc)));
        SearchResponse<LostESDTO> response = esClient.search(
                searchBuilder.build(),
                LostESDTO.class);
        HitsMetadata<LostESDTO> hitsMetadata = response.hits();
        List<Hit<LostESDTO>> hits = hitsMetadata.hits();
        // hits.forEach(hit->hit.);
        // 构造分页对象
        Page<LostESDTO> page = new Page<>();

        if (Objects.nonNull(response.hits().total())) {
            page.setTotal(response.hits().total().value());
        }

        // List<Hit<LostESDTO>> hits = response.hits().hits();
        List<LostESDTO> lostList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(hits)) {
            lostList = hits.stream().map(hit -> {
                        LostESDTO source = hit.source();
                        Map<String, List<String>> highlightMap = hit.highlight();
                        if (highlightMap.containsKey("title")) {
                            source.setTitle(highlightMap.get("title").get(0));
                        }
                        if (highlightMap.containsKey("descrip")) {
                            source.setDescrip(highlightMap.get("descrip").get(0));
                        }
                        if (highlightMap.containsKey("nickname")) {
                            source.setNickname(highlightMap.get("nickname").get(0));
                        }
                        return source;
                    })
                    .collect(Collectors.toList());
        }
        return page.setRecords(lostList);
    }

    @Override
    public LostVO getVOById(Long id) {
        ThrowUtils.throwIf(Objects.isNull(id) || id <= 0,
                ReturnCodeEnum.PARAMS_ERROR);
        Lost lost = this.getBaseMapper().selectById(id);
        LostVO lostVO = new LostVO();
        BeanUtils.copyProperties(lost, lostVO);
        return lostVO;
    }

    @Override
    public boolean changeStatus(Long id, Integer status) {
        // 校验参数id是否合法
        ThrowUtils.throwIf(Objects.isNull(id) || id < 0, ReturnCodeEnum.PARAMS_ERROR);
        // 校验status参数是否合法
        ThrowUtils.throwIf(Objects.isNull(status) ||
                        !LostConstant.LOST_STATUS_LIST.contains(status)
                , ReturnCodeEnum.PARAMS_ERROR);

        Lost dbLost = this.getBaseMapper().selectById(id);
        ThrowUtils.throwIf(Objects.isNull(dbLost), ReturnCodeEnum.PARAMS_ERROR);
        dbLost.setStatus(status);
        if (this.getBaseMapper().updateById(dbLost) <= 0) {
            return false;
        }
        LostESDTO lostESDTO = new LostESDTO();
        BeanUtils.copyProperties(dbLost, lostESDTO);
        lostESDao.save(lostESDTO);
        return true;
    }
}




